前言
关于localStorage
sessionStorage
之类的api怎么用已经无需我再赘述了,但是具体怎么落实到一个稍微复杂一些的业务中还是需要做一些前期的准备
遇见的一些问题
1.localStorage
与 sessionStorage
具体适用于什么样的业务场景?
2.如何维护本地储存?
3.如何进行版本控制?
4.碰到禁止本地缓存的情况下怎么解决这个问题?
常见情况
根据本身特性localStorage
做长期存储, sessionStorage
做临时存储大家都是清楚的也无需赘述,至于IndexedDB
之类的暂时不做讨论。
很多初级的前后端分离的项目习惯性误区是把用户token存到localStorage。
这时候碰到一个问题:
如果需要在不同域名下做登陆信息共享该怎么办?
常见做法是SSO
,同一个域名下的小型项目用cookies
的domain
直接来做简单的跨域共享也是一个办法,而这个时候localStorage
和sessionStorage
就出现业务盲点了。
不仅过不去,即使通过各种鬼畜的操作过去了后面擦屁股也麻烦的要死:
- 登陆了以后用户信息怎么传给别的域名?
- 退登的话怎么删除所有页面的登陆状态?
当然如果需要实现还是比较依赖后端的用户登陆状态判定,而这个时候某些分离得过分干净的业务可能出现一些诡异的状态判定,比如a.xxx.com站退登了b.xxx.com站还登着,你要真要前端来做这事代码量也是相当可观的,甚至各个页面缓存的token数据可能还都不一样,显然不够合理也不适合这样用,请把后端的事情还给后端,除非前端也写一些node中间层。
而且某些浏览器的无痕功能禁用了localStorage和sessionStorage导致登陆状态异常,所以localStorage
存用户token的事情就把它忘记吧,不然后面就等着哭吧。
那么存什么好呢?
存些不常变的东西?
比如图片,把图片转成base64
再存起来?
那么问题来了,一般我们会存什么图片?
比如一些背景图?
那么为了这件事情我们首次载入的时候先要把图片下载过来,然后费尽心思存起来还要避免和别的同事重名,万一哪天要换个图片还要想办法把之前的内容清理掉不然直接读localStorage
里的内容,即使做好了本地存储的版本控制还要专门写一大堆代码增删改查,这明显是嫌自己工作量不够大,本来只要用cdn直接解决的东西被搞得这么费劲,何必呢?
所以存文件的事情基本也可以忽略了。
还有一些神奇的做法是存js代码,敢这样做的人不是蠢就是坏了。
然后,一般localStorage
常用于缓存一些内容很多很固定的数据比如全国各地省市县、区号之类的基本不会变但又会在各种ui组件里用到的简单数据,但是直接JSON.parse
然后还去遍历一个很大的数据做查询筛选只会让你的机器感到绝望发红发烫濒临崩溃,而这个时候我还不如直接用cdn载入一个专门用于存放这类静态数据的js文件来得快捷方便性能还好还能随时改。
难道localStorage
真的这么废?
其实也不是啦,首先要确定一点,不去存过大的JSON数据,那就存个小的:
- 用户搜索历史的缓存。
- 文本编辑器内输入的内容。
然后是sessionStorage
,其实我最喜欢用这个了,关掉就清除毫无后顾之忧,那一般我会用于什么业务场景呢:
- 存网页的历史记录,操作过
histroy
API的朋友一定知道为了避免安全性问题histroy只会给你当前页面前面打开过几个页面的数量。所以完全可以在这里做个histroy的详细记录,甚至结合spa项目的router插件来满足各类奇怪的需求,比如跳了好几次页面填了一堆表格后点击支付然后支付完成后即使点击浏览器顶部的后退按钮也能直接一脚跨回首页。- 或者存那堆临时表格的内容(2B业务经常碰到那种填一大堆又臭又长的申请表还要经过各种审批的功能)
- 存用户个人信息,比如用户昵称之类的,不用每次刷新都去服务器拖一遍(当然这种事情其实还是有一定风险的,万一用户在别的地方改昵称了,你还得想办法同步,具体解决办法后面说)
怎么存?
很多人第一反应可能是直接使用 localStorage.setItem
之类的api
一旦你真的只这样做了...我可能无法保证你不被同事揍
入口
每个业务线本身应该有个状态的管理区域,而这些业务线的本地数据存储业务应该汇总入到一个公共的入口做类型判定以及进行增删改查。
一般比如Vue
的应用,我们会把数据存到Vuex的state
内,每个业务线分支都会有单独的模块引入并进行独立维护。
命名
你必须确定一个习惯一致,名字唯一的命名协议。
自娱自乐的项目也就罢了,万一是个超过四个人的前端小队每年可能都有不同的人会离职不同的人会入职不同的业务要迭代不同的功能要修改,增删改查的事情如果没有一个确定的标准后果不堪设想。
一般常见命名方式会在前面带入业务模块名称
比如 USER_INFORMATION_NICKNAME
之类的,当然有的信息可能不方便透露只写个索引名称也是有的,主要还是看公司里决定这个规则的人怎么考虑。
增删改查
还是以Vuex
做为例子(以下内容仅供参考,请勿直接用于业务代码中)
在State
中声明对象用于获取本地存储内容的元数据
state:{
NICKNAME: window.localStorage.getItem('USER_INFORMATION_NICKNAME')
}
创建一个Getter
用于内容读取
getters: {
USER_INFORMATION_NICKNAME: state => {
try {
return JSON.parse(state.NICKNAME)
} catch (e) {
localStorage.removeItem('USER_INFORMATION_NICKNAME')
return null
}
}
}
写入Mutation
用于增删改
mutations: {
DELETE_USER_INFORMATION_NICKNAME (state) {
window.localStorage.removeItem('USER_INFORMATION_NICKNAME')
state.USER_INFORMATION_NICKNAME = value
},
UPDATE_USER_INFORMATION_NICKNAME (state, value) {
window.localStorage.setItem('USER_INFORMATION_NICKNAME', value)
state.USER_INFORMATION_NICKNAME = null
}
}
写入Action
用于信息同步
actions: {
GET_USER_INFORMATION_NICKNAME:context => {
$http
.get('xxx')
.then(res=> {
context.commit('UPDATE_INFORMATION_NICKNAME', res)
})
.catch(e => xxx...)
}
}
这样一个最最最基本的读取策略已经完成了。
但问题又来了
怎么删?什么时候删?
毕竟是永久缓存,版本控制非常重要,不然就是给自己挖坑
首先要明确删除数据的具体场景
- 可能是只存一小段时间后就失效的内容
- 可能是下个大版本会被迭代掉的内容
- 可能会发起部分用户要升级部分用户维持现状甚至因为新版本业务不够完善可能需要回滚的灰度更新。
一般情况下我们需要在getter内先确定一个数据读取的依赖项,读取的内容可能是跟着依赖项数据走也可能不跟着依赖项数据走,这个看具体业务需求,如果不跟着依赖项走或者本身就是被依赖的项目的话就需要确定几件事情
- 版本
- 过期时间
可能该内容依赖于父级项 0.0.1
版本的内容,超过或者低于这个版本都可能出现问题需要及时删除并重新获取
可能当前载入的页面中的配置项的版本与自身版本不一致所以需要移除并更新数据
于是导致的结果是我们需要再去声明一个不保存在本地的配置JSON
{
"USER_INFORMATION": {
"NICKNAME": "1.1.1",
"AGE": "1.1.2"
...
}
}
如果要进行灰度更新的话,这个配置就需要写入到接口里面了。
还有个情况就是设置过期时间,超出这个限定的时间就清空数据。
于是我们就需要在UPDATE
方法内多写入些东西
UPDATE_USER_INFORMATION_NICKNAME (state, value) {
const new_value = {
expires: Date.now() + 24 * 1000 * 30 * 3600,
version: state.config.USER_INFORMATION.NICKNAME,
value: value
}
window.localStorage.setItem('USER_INFORMATION_NICKNAME', JSON.stringify(new_value))
state.USER_INFORMATION_NICKNAME = new_value
}
再调整下读取的逻辑,
其他的代码也跟着做一遍调整,依照自己能力水平能封装的封装,能复用的复用。
于是一个拥有版本控制和过期控制的本地内容存储功能模块就算初步完成了。
做完这一切虽然感觉好像是像那么回事了
直到用户突然开启了无痕模式....
后面的问题也只是封装业务中的判断逻辑罢了...
所以,有一个健壮的前端数据流才是核心,其他的只不过是帮助页面达到更好的体验的辅助功能罢了,东西再好也不能滥用。
最后
基于这个事情的考虑,于是顺便写了个本地存储控制的库,api基本都在上面了
GITHUB:steerable-storrage
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。